﻿/**
\file
\version 1.0
\date    22/06/2016
\author  JGB
\brief   Qt C++ 中的 JWT (JSON Web Token) 实现
*/

// MIT 许可证(MIT)
// 版权所有(c) <2016> <Juan Gonzalez Burgos>
// 特此授予获得本软件及其相关文档文件（“软件”）副本的任何人免费使用、复制、修改、合并、发布、分发、再许可和/或销售该软件副本的权利，并允许向其提供软件的人这样做，前提是遵守以下条件：
// 上述版权声明和本许可声明应包含在所有副本或重要部分的软件中。
// 该软件按“原样”提供，不附带任何明示或暗示的保证，包括但不限于对适销性、特定用途适用性和非侵权性的保证。在任何情况下，作者或版权持有人均不对任何索赔、损害或其他责任负责，无论是在合同诉讼、侵权诉讼还是其他诉讼中，因软件或与软件使用或其他交易有关的任何事宜而产生。

#ifndef QJSONWEBTOKEN_H
#define QJSONWEBTOKEN_H

#include <QMessageAuthenticationCode>
#include <QJsonDocument>
#include <QJsonObject>

/**
\brief   QJsonWebToken : Qt C++ 中的 JWT (JSON Web Token) 实现

## 引言

此类实现了 [JSON Web Token](https://en.wikipedia.org/wiki/JSON_Web_Token) 开放标准 [RFC 7519](https://tools.ietf.org/html/rfc7519) 的一部分。

目前此实现仅支持以下算法：

Alg   | 参数值                          | 算法
----- | ------------------------------- | ------------------------------
HS256 | 使用 SHA-256 哈希算法的 HMAC
HS384 | 使用 SHA-384 哈希算法的 HMAC
HS512 | 使用 SHA-512 哈希算法的 HMAC

### 包含

为了在项目中包含此类，在 qt 项目的 **.pro** 文件中添加以下行：

```
HEADERS  += ./src/qjsonwebtoken.h
SOURCES  += ./src/qjsonwebtoken.cpp
```

### 使用

该项目的仓库中包含了一些示例，展示了如何使用此类：

* ./examples/jwtcreator/  : 展示如何创建带有自定义 *payload* 的 JWT 的示例。

* ./examples/jwtverifier/ : 展示如何使用给定的 *secret* 验证 JWT 的示例。
*/
class Q_DECL_EXPORT QJsonWebToken
{

public:

    /**
\brief 构造函数。
\return 返回一个新的 QJsonWebToken 实例。

创建一个默认的 QJsonWebToken 实例，使用 *HS256 算法*，*payload* 为空，*secret* 也为空。
*/
    QJsonWebToken();                            // TODO : improve with params

    /**

    \brief Copy Construtor.
    \param other Other QJsonWebToken to copy from.
    \return A new instance of QJsonWebToken with same contents as the *other* instance.

    Copies to the new instance the JWT *header*, *payload*, *signature*, *secret* and *algorithm*.

    */
    QJsonWebToken(const QJsonWebToken &other);

    /**

    \brief Returns the JWT *header* as a QJsonDocument.
    \return JWT *header* as a QJsonDocument.

    */
    QJsonDocument getHeaderJDoc() const;

    /**

    \brief Returns the JWT *header* as a QString.
    \param format Defines the format of the JSON returned.
    \return JWT *header* as a QString.

    Format can be *QJsonDocument::JsonFormat::Indented* or *QJsonDocument::JsonFormat::Compact*

    */
    QString       getHeaderQStr() const;

    /**

    \brief Sets the JWT *header* from a QJsonDocument.
    \param jdocHeader JWT *header* as a QJsonDocument.
    \return true if the header was set, false if the header was not set.

    This method checks for a valid header format and returns false if the header is invalid.

    */
    bool          setHeaderJDoc(const QJsonDocument &jdocHeader);

    /**

    \brief Sets the JWT *header* from a QString.
    \param jdocHeader JWT *header* as a QString.
    \return true if the header was set, false if the header was not set.

    This method checks for a valid header format and returns false if the header is invalid.

    */
    bool          setHeaderQStr(const QString &strHeader);

    /**

    \brief Returns the JWT *payload* as a QJsonDocument.
    \return JWT *payload* as a QJsonDocument.

    */
    QJsonDocument getPayloadJDoc() const;

    /**

    \brief Returns the JWT *payload* as a QString.
    \param format Defines the format of the JSON returned.
    \return JWT *payload* as a QString.

    Format can be *QJsonDocument::JsonFormat::Indented* or *QJsonDocument::JsonFormat::Compact*

    */
    QString       getPayloadQStr() const;

    /**

    \brief Sets the JWT *payload* from a QJsonDocument.
    \param jdocHeader JWT *payload* as a QJsonDocument.
    \return true if the payload was set, false if the payload was not set.

    This method checks for a valid payload format and returns false if the payload is invalid.

    */
    bool          setPayloadJDoc(const QJsonDocument &jdocPayload);

    /**

    \brief Sets the JWT *payload* from a QString.
    \param jdocHeader JWT *payload* as a QString.
    \return true if the payload was set, false if the payload was not set.

    This method checks for a valid payload format and returns false if the payload is invalid.

    */
    bool          setPayloadQStr(const QString &strPayload);

    /**

    \brief Returns the JWT *signature* as a QByteArray.
    \return JWT *signature* as a decoded QByteArray.

    Recalculates the JWT signature given the current *header*, *payload*, *algorithm* and
    *secret*.

    \warning This method overwrites the old signature internally. This could be undesired when
    the signature was obtained by copying from another QJsonWebToken using the copy constructor.

    */
    QByteArray    getSignature(); // WARNING : non-const because it overwrites signature

    /**

    \brief Returns the JWT *signature* as a QByteArray.
    \return JWT *signature* as a **base64 encoded** QByteArray.

    Recalculates the JWT signature given the current *header*, *payload*, *algorithm* and
    *secret*. Then encodes the calculated signature using base64 encoding.

    \warning This method overwrites the old signature internally. This could be undesired when
    the signature was obtained by copying from another QJsonWebToken using the copy constructor.

    */
    QByteArray    getSignatureBase64(); // WARNING : non-const because it overwrites signature

    /**

    \brief Returns the JWT *secret* as a QString.
    \return JWT *secret* as a QString.

    */
    QString       getSecret() const;

    /**

    \brief Sets the JWT *secret* from a QString.
    \param strSecret JWT *secret* as a QString.
    \return true if the secret was set, false if the secret was not set.

    This method checks for a valid secret format and returns false if the secret is invalid.

    */
    bool          setSecret(const QString &strSecret);

    /**

    \brief Creates and sets a random secret.

    This method creates a random secret with the length defined by QJsonWebToken::getRandLength(),
    and the characters defined by QJsonWebToken::getRandAlphanum().

    \sa QJsonWebToken::getRandLength().
    \sa QJsonWebToken::getRandAlphanum().

    */
    void          setRandomSecret();

    /**

    \brief Returns the JWT *algorithm* as a QString.
    \return JWT *algorithm* as a QString.

    */
    QString       getAlgorithmStr() const;

    /**

    \brief Sets the JWT *algorithm* from a QString.
    \param strAlgorithm JWT *algorithm* as a QString.
    \return true if the algorithm was set, false if the algorithm was not set.

    This method checks for a valid supported algorithm. Valid values are:

    "HS256", "HS384" and "HS512".

    \sa QJsonWebToken::supportedAlgorithms().

    */
    bool          setAlgorithmStr(const QString &strAlgorithm);

    /**

    \brief Returns the complete JWT as a QString.
    \return Complete JWT as a QString.

    The token has the form:

    ```
    xxxxx.yyyyy.zzzzz
    ```

    where:

    - *xxxxx* is the *header* enconded in base64.
    - *yyyyy* is the *payload* enconded in base64.
    - *zzzzz* is the *signature* enconded in base64.

    \warning This method overwrites the old signature becuse it calls getSignatureBase64 internally.
    This could be undesired when the signature was obtained by copying from another QJsonWebToken
    using the copy constructor.

    \sa QJsonWebToken::getSignatureBase64().

    */
    QString       getToken();

    /**

    \brief Sets the complete JWT as a QString.
    \param strToken Complete JWT as a QString.
    \return true if the complete JWT was set, false if not set.

    This method checks for a valid JWT format. It overwrites the *header*,
    *payload* , *signature* and *algorithm*. It does **not** overwrite the secret.

    \sa QJsonWebToken::getToken().

    */
    bool          setToken(const QString &strToken);

    /**

    \brief Returns the current set of characters used to create random secrets.
    \return Set of characters as a QString.

    The default value is "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

    \sa QJsonWebToken::setRandomSecret()
    \sa QJsonWebToken::setRandAlphanum()

    */
    QString       getRandAlphanum() const;

    /**

    \brief Sets the current set of characters used to create random secrets.
    \param strRandAlphanum Set of characters as a QString.

    \sa QJsonWebToken::setRandomSecret()
    \sa QJsonWebToken::getRandAlphanum()

    */
    void          setRandAlphanum(const QString &strRandAlphanum);

    /**

    \brief Returns the current length used to create random secrets.
    \return Length of random secret as a QString.

    The default value is 10;

    \sa QJsonWebToken::setRandomSecret()
    \sa QJsonWebToken::setRandLength()

    */
    int           getRandLength() const;

    /**

    \brief Sets the current length used to create random secrets.
    \param intRandLength Length of random secret.

    \sa QJsonWebToken::setRandomSecret()
    \sa QJsonWebToken::getRandLength()

    */
    void          setRandLength(const int &intRandLength);

    /**

    \brief Checks validity of current JWT with respect to secret.
    \return true if the JWT is valid with respect to secret, else false.

    Uses the current *secret* to calculate a temporary *signature* and compares it to the
    current signature to check if they are the same. If they are, true is returned, if not then
    false is returned.

    */
    bool          isValid() const;

    /**

    \brief Creates a QJsonWebToken instance from the complete JWT and a secret.
    \param strToken Complete JWT as a QString.
    \param srtSecret Secret as a QString.
    \return Instance of QJsonWebToken.

    The JWT provided must have a valid format, else a QJsonWebToken instance with default
    values will be returned.

    */
    static QJsonWebToken fromTokenAndSecret(const QString &strToken, const QString &srtSecret);

    /**

    \brief Returns a list of the supported algorithms.
    \return List of supported algorithms as a QStringList.

    */
    static QStringList supportedAlgorithms();

    /**

    \brief Convenience method to append a claim to the *payload*.
    \param strClaimType The claim type as a QString.
    \param strValue The value type as a QString.

    Both parameters must be non-empty. If the claim type already exists, the current
    claim value is updated.

    */
    void appendClaim(const QString &strClaimType, const QString &strValue);
    //颜磊添加 全类型jsonvalue
    void appendClaim(const QString &strClaimType, const QJsonValue &value);

    /**

    \brief Convenience method to remove a claim from the *payload*.
    \param strClaimType The claim type as a QString.

    If the claim type does not exist in the *payload*, then this method does nothins.

    */
    void removeClaim(const QString &strClaimType);

    /**
    \brief 方便方法，用于从 *payload* 中返回某个 claim 的数据作为字符串。
    \param strClaimType The claim type as a QString.

    如果 *payload* 中不存在该 claim 类型，则此方法返回一个默认构造的 QString。

    */
    QString claim(const QString &strClaimType);

private:
    // properties
    QJsonDocument m_jdocHeader;	   // unencoded
    QJsonDocument m_jdocPayload;   // unencoded
    QByteArray    m_byteSignature; // unencoded
    QString       m_strHeader; // unencoded
    QString       m_strPayload; // unencoded

    QString       m_strSecret;
    QString       m_strAlgorithm;

    int           m_intRandLength  ;
    QString       m_strRandAlphanum;

    // helpers
    QByteArray    m_byteAllData;

    bool isAlgorithmSupported(const QString &strAlgorithm);
};

#endif // QJSONWEBTOKEN_H
